/* Copyright © 2010 LPRM
Universidade Federal do Espírito Santo - RNP-GT CWTools

This file is part of GW-Android.

GW-Android is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

GW-Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with GW-Android. If not, see <http://www.gnu.org/licenses/>. */
package br.ufes.cwtools.gw.android.components.gallery;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
//import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
//import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.Toast;
import br.ufes.cwtools.gw.android.components.Component;
import br.ufes.cwtools.gw.android.connection_service.ConnectionServiceConnection;
import br.ufes.cwtools.gw.android.connection_service.IConnectionService;
import br.ufes.cwtools.gw.android.util.Base64;

// TODO: Implementar um botão de back que só aparece quando é feita uma busca. Pra voltar pra INDEX
// Adicionar um label com o título da página: Fotos Mais Recentes ou Resultados da Busca
// Já criei um enum de estados possíveis da UI pra facilitar o uso do botão BACK do Android pra isso

public class Gallery extends Component {
	private static enum MessageType { INVALID, XML, IMAGE_THUMB, IMAGE }
	private static enum UIState { INDEX, SEARCH, DETAILS }
	private static final String LOG_TAG = "ImageGallery";
	private static final int NUM_FOTOS_INDEX = 18;
	private static final String MESSAGE_REQUEST_ID = "requestId";
	
	private String mThumbUrlPrefix;
	private String mImageUrl;
	private String mSearchUrl;
	private String mAdvancedSearchUrl;
	private String mIndexUrl;

	private GridView mGalleryGridView;
	private ImageAdapter mImageAdapter;
	private RelativeLayout mSearchLayout;
	private LinearLayout mAdvancedSearchLayout;
	private RadioButton mAdvancedSearchToggle;
	private LinearLayout mSingleImageLayout;
	private WebView mSingleImageView;
	private EditText mSearchField;
	private EditText mDescriptionField;
	private EditText mPlaceField;
//	private DatePicker mDatePicker;
	
	private Toast mToast;
	private ProgressDialog mDialog;
	private UIState mState = UIState.INDEX;
	private boolean mSearched = false;
	
	private Map<Photo, Drawable> mPhotos = new HashMap<Photo, Drawable>();
	private Map<String, Photo> mThumbsRequest = new HashMap<String, Photo>();
	private String mSingleImageRequestId;
	private String mSearchRequestId;
	
	@Override
	public void onBackPressed() {
		if (mAdvancedSearchToggle.isChecked()){
			// Close the advanced search layout
			advancedSearchOnClick(mAdvancedSearchToggle);
			return;
		}
		
		switch(mState){
			case DETAILS:
				mSingleImageView.clearView();
				mSingleImageLayout.setVisibility(View.GONE);
				mSearchLayout.setVisibility(View.VISIBLE);
				mGalleryGridView.setVisibility(View.VISIBLE);
				if(mSearched){
					mState = UIState.SEARCH;
				}else{
					mState = UIState.INDEX;
				}
				break;
			case SEARCH:
				setupDialog();
				requestIndex();
				mState = UIState.INDEX;
				mSearched = false;
				mSearchField.getText().clear();
				break;
			case INDEX:
			default:
				super.onBackPressed();
				break;
		}
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.component_image_gallery);
		
		String appUrl = getIntent().getStringExtra(Component.EXTRA_APPLICATION_URL);
		String baseUrl = getIntent().getStringExtra(Component.EXTRA_BASE_URL); // baseUrl = "/groupware-workbench/photo/6/index"
		String baseUrlParts[] = baseUrl.split("/");

		mThumbUrlPrefix = appUrl + "/" + baseUrlParts[1] + "/" + baseUrlParts[2] + "/img-crop/";
		mImageUrl = appUrl + "/" + baseUrlParts[1] + "/" + baseUrlParts[2] + "/img-show/";
		baseUrl = appUrl + "/" + baseUrlParts[1] + "/" + baseUrlParts[2] + "/" + baseUrlParts[3];
		mSearchUrl = baseUrl + "/busca?_format=xml";
		mAdvancedSearchUrl = baseUrl + "/buscaAvancada?_format=xml";
		mIndexUrl = baseUrl + "/listbypage/" + NUM_FOTOS_INDEX + "/0?_format=xml";

		mSearchLayout = (RelativeLayout) findViewById(R.id.searchLayout);
		mSearchField = (EditText) findViewById(R.id.searchField);
		mAdvancedSearchLayout = (LinearLayout) findViewById(R.id.advancedSearchLayout);
		mAdvancedSearchToggle = (RadioButton) findViewById(R.id.advancedSearchToggle);
//		Calendar now = Calendar.getInstance();
//		mDatePicker = (DatePicker) findViewById(R.id.datePicker);
//		mDatePicker.updateDate(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH));

		mImageAdapter = new ImageAdapter(this, R.layout.image_grid_item);
		mGalleryGridView = (GridView) findViewById(R.id.galleryView);
		mGalleryGridView.setAdapter(mImageAdapter);
		mSingleImageLayout = (LinearLayout) findViewById(R.id.singleImageLayout);
		mSingleImageView = (WebView) findViewById(R.id.singleImageView);
		mSingleImageView.getZoomControls().setVisibility(View.VISIBLE);
		mGalleryGridView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
                try {
                    mSingleImageRequestId = ConnectionServiceConnection.generateId();
                    String imageId = ((Photo) parent.getItemAtPosition(position)).getId();
                    mConnection.registerGetRequest(mImageUrl + imageId, mSingleImageRequestId, true);
                    mGalleryGridView.setVisibility(View.GONE);
                    mSearchLayout.setVisibility(View.GONE);
                    mSingleImageLayout.setVisibility(View.VISIBLE);
                    mSingleImageView.loadUrl("file:///android_asset/loading.html");
                    mState = UIState.DETAILS;
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Erro na visualização em detalhes da imagem.");
                }
            }
        });
		
		bindService(new Intent(IConnectionService.class.getName()), mConnection, Context.BIND_AUTO_CREATE);
		setupDialog();
	}
	
	private void setupDialog(){
		mDialog = new ProgressDialog(Gallery.this);
		mDialog.setTitle("Buscando fotos");
		mDialog.setMessage("Aguarde enquanto a busca é efetuada.");
		mDialog.setIndeterminate(true);
		mDialog.setCancelable(true);
		mDialog.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				finish();
				
			}
		});
		mDialog.show();
	}
	
	@Override
	protected void onDestroy() {
		try {
			mConnection.unregisterAll();
			unbindService(mConnection);
		} catch (RemoteException e) { }
		super.onDestroy();
	}
	
	private void requestThumbsAndSetupGrid(List<Photo> photos){
		mThumbsRequest.clear();
		mPhotos.clear();
		for(Photo p : photos){
			String photoRequestId = ConnectionServiceConnection.generateId();
			String photoUrl = mThumbUrlPrefix + p.getId();
			mThumbsRequest.put(photoRequestId, p);
			try {
				mConnection.registerGetRequest(photoUrl, photoRequestId, true);
			} catch (Exception e) {
				Log.e(LOG_TAG, "Erro na requisição de thumbnail. Url: " + photoUrl + " | Foto: " + p.getNome(), e);
			}
		}
	}
	
	private final ConnectionServiceConnection mConnection = new ConnectionServiceConnection(Gallery.class.getName()) {
		@Override
		public void onServiceConnected() {
			requestIndex();
		}
		
		@Override
		public void onRequestFinished(String id, String filePath) {
			MessageType messageType = getRequestTypeById(id);
			Log.d(LOG_TAG,"Chamando ImageHandler");
			switch (messageType) {
				case IMAGE_THUMB:
					Message m = mHandler.obtainMessage(messageType.ordinal(), filePath);
					Bundle b = new Bundle();
					b.putString(MESSAGE_REQUEST_ID, id);
					m.setData(b);
					mHandler.sendMessage(m);
					break;
				case IMAGE:
				case XML:
					mHandler.sendMessage(mHandler.obtainMessage(messageType.ordinal(), filePath));
					break;
				case INVALID:
				default:
					Log.d(LOG_TAG, "Erro no retorno do download.");
					break;
			}
		}
	};
	
	private void requestIndex(){
		try {
			mSearchRequestId = ConnectionServiceConnection.generateId();
			mConnection.registerGetRequest(mIndexUrl, mSearchRequestId, false);
		} catch (Exception e) {
			Log.e(LOG_TAG, "Erro no pedido de download do index da galeria.", e);
		}
	}
	
	private MessageType getRequestTypeById(String id){
		if (id.equals(mSearchRequestId)){
			return MessageType.XML;
		}else if (id.equals(mSingleImageRequestId)){
			return MessageType.IMAGE;
		}
		for (String requestId : mThumbsRequest.keySet()){
			if (requestId.equals(id)){
				return MessageType.IMAGE_THUMB;
			}
		}
		return MessageType.INVALID;
	}
	
	private Handler mHandler = new Handler() {
		String filePath;
		@Override
		public void handleMessage(Message msg) {
			MessageType[] types = MessageType.values();
			MessageType messageType;
			if (msg.what < 0 || msg.what >= types.length){
				messageType = MessageType.INVALID;
			}else{
				messageType = types[msg.what];
			}
			
			switch (messageType) {
				case IMAGE_THUMB:
					try {
						filePath = (String) msg.obj;
						Drawable d = new BitmapDrawable(filePath);
						String requestId = msg.getData().getString(MESSAGE_REQUEST_ID);
						Photo p = mThumbsRequest.get(requestId);
						mPhotos.put(p, d);
						mImageAdapter.add(p);
					} catch (Exception e) {
						Log.e(LOG_TAG, "Erro na leitura de arquivo.", e);
					}
					if (mThumbsRequest.size() == mPhotos.size()){
						mToast.cancel();
					}
					break;
				case XML:
					filePath = (String) msg.obj;
					try {
						InputStream source = new FileInputStream(filePath);
						List<Photo> photoList = PhotoParser.parse(source);
						mImageAdapter.clear();
						
						// Atualizando o estado da UI
						mDialog.dismiss();
						mDialog = null;
						mToast = Toast.makeText(Gallery.this, "Carregando fotos encontradas...", Toast.LENGTH_SHORT);
						mToast.show();
						
						requestThumbsAndSetupGrid(photoList);
					} catch (Exception e) {
						Log.e(LOG_TAG, "Erro na leitura de arquivo.", e);
					}
					break;
				case IMAGE:
					filePath = (String) msg.obj;
					try {
						String base64Image = Base64.encodeFromFile(filePath);
						String dataUrl = "data:image/jpeg;base64," + base64Image;
						mSingleImageView.loadData("<img src='" + dataUrl + "' />", "text/html", "UTF-8");
						Log.v(LOG_TAG, dataUrl);
					} catch (Exception e) {
						Log.e(LOG_TAG, "Erro na leitura de arquivo.", e);
					}
					break;
				case INVALID:
				default:
					super.handleMessage(msg);
				}
		}
	};
	
	public void advancedSearchOnClick(View view) {
		if (mAdvancedSearchLayout.getVisibility() == View.GONE) {
			mAdvancedSearchLayout.setVisibility(View.VISIBLE);
			mAdvancedSearchToggle.setChecked(true);
		} else {
			mAdvancedSearchLayout.setVisibility(View.GONE);
			mAdvancedSearchToggle.setChecked(false);
		}
	}

	public void searchOnClick(View view) {
		String termoBusca = mSearchField.getText().toString();
		
		if (mAdvancedSearchToggle.isChecked()){ // Busca Avançada
			String buscaAvancadaDescricao = mDescriptionField.getText().toString();
			String buscaAvancadaLugar = mPlaceField.getText().toString();
//			String strDay = mDatePicker.getDayOfMonth() < 10 ? "0" + Integer.toString(mDatePicker.getDayOfMonth()) : Integer.toString(mDatePicker.getDayOfMonth());
//			String strMonth = mDatePicker.getMonth() < 9 ? "0" + Integer.toString(mDatePicker.getMonth() + 1) : Integer.toString(mDatePicker.getMonth());
//			String data = strDay + "/" + strMonth + "/" + mDatePicker.getYear();
			try {
				Bundle stringParts = new Bundle();
				stringParts.putString("nome", termoBusca);
				stringParts.putString("descricao", buscaAvancadaDescricao);
				stringParts.putString("lugar", buscaAvancadaLugar);
				stringParts.putString("date", "");
				mSearchRequestId = ConnectionServiceConnection.generateId();
				mConnection.registerPostRequest(mAdvancedSearchUrl, mSearchRequestId, stringParts, new Bundle());
			} catch (RemoteException e) {
				Log.e(LOG_TAG, "Erro na busca avançacada.", e);
			}
		} else { // Busca simples
			try {
				Bundle stringParts = new Bundle();
				stringParts.putString("busca", termoBusca);
				mSearchRequestId = ConnectionServiceConnection.generateId();
				mConnection.registerPostRequest(mSearchUrl, mSearchRequestId, stringParts, new Bundle());			
			} catch (RemoteException e) {
				Log.e(LOG_TAG, "Erro na busca simples.", e);
			}
		}
		mAdvancedSearchToggle.setChecked(false);
		mAdvancedSearchLayout.setVisibility(View.GONE);
		mGalleryGridView.setVisibility(View.VISIBLE);
		
		mState = UIState.SEARCH;
		mSearched = true;
		//Cria o dialog de loading
		mDialog = new ProgressDialog(Gallery.this);
		mDialog.setTitle("Buscando fotos");
		mDialog.setMessage("Aguarde enquanto a busca é efetuada.");
		mDialog.setIndeterminate(true);
		mDialog.setCancelable(true);
		mDialog.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				mState = UIState.INDEX;
				mDialog.dismiss();
				mDialog = null;
				try {
					mConnection.unregister(mSearchRequestId);
				} catch (Exception e) {
					Log.e(LOG_TAG, "Erro no cancelamento da busca.", e);
				}
				mSearchRequestId = null;
			}
		});
		mDialog.show();
	}
	
	private class ImageAdapter extends ArrayAdapter<Photo> {
		public ImageAdapter(Context context, int textViewResourceId) {
			super(context, textViewResourceId);
		}
		
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View view = getLayoutInflater().inflate(R.layout.image_grid_item, parent, false);
			((ImageView) view.findViewById(R.id.image)).setImageDrawable(mPhotos.get(getItem(position)));
			return view;
		}
	}
	
	static final class Photo {
		private String id;
		private String nome;
		private String descricao;
		private String dataUpload;
		private String lugar;
		
		public Photo() {}
		public String getId() { return id; }
		public void setId(String id) { this.id = id; }
		public String getNome() { return nome; }
		public void setNome(String nome) { this.nome = nome; }
		public String getDescricao() { return descricao; }
		public void setDescricao(String descricao) { this.descricao = descricao; } 
		public String getDataUpload() { return dataUpload; }
		public void setDataUpload(String dataCriacao) { this.dataUpload = dataCriacao; }
		public String getLugar() { return lugar; }
		public void setLugar(String lugar) { this.lugar = lugar; }
	}
	
	static final class PhotoParser {
		private static ArrayList<Photo> listaDeFotos;
		private static Document document;
		
		@SuppressWarnings("unchecked")
		public static List<Photo> parse(InputStream source){
			listaDeFotos = new ArrayList<Photo>();
			try {
				document = new SAXReader().read(source);
				
				List<Node> fotoNodes = document.selectNodes("//list/photo");

				for (Node xmlNode : fotoNodes){
		        	Photo photo = new Photo();
		        	
		        	photo.setId(xmlNode.valueOf("id"));
		        	photo.setNome(xmlNode.valueOf("nome"));
		        	photo.setDescricao(xmlNode.valueOf("descricao"));
		        	photo.setLugar(xmlNode.valueOf("lugar"));
		        	photo.setDataUpload(xmlNode.valueOf("dataUpload"));
		        	listaDeFotos.add(photo);
		        }
				
				return listaDeFotos;
			} catch (DocumentException e) {
				e.printStackTrace();
			}
			return null;
		}
	}
}